#pragma once

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "../comm/util.h"
#include "../comm/Log.h"

using namespace std;

// 编译功能：用于编译提交的代码文件, 将代码的编译结果返回到./temp/test.compile_err临时文件中
namespace ns_compile
{
    using namespace ns_util;
    using namespace ns_log;

    class compiler
    {
    public:
        compiler() {}
        ~compiler() {}

    public:
        //************************************************************************
        // 参数：
        // file_name: 需要在./temp下编译的代码文件文件(file_name不带后缀.cpp)
        //
        // 返回值：
        // 编译成功返回true, 否则返回false
        // 并且错误时会将错误信息写入到./temp下后缀为.compile_err的文件里
        //************************************************************************
        static bool compile(const string &file_name)
        {
            // 执行编译功能 g++ -o test.exe test，参数是文件名称

            // 0.提前存好编译时的临时文件名称
            string _compile_err = ns_util::path_util::Compile_err(file_name);
            string _src = ns_util::path_util::Src(file_name);
            string _exe = ns_util::path_util::Exe(file_name);

            //  1.创建子进程
            //  2.让子进程进行程序替换执行代码编译
            //  3.编译的结果会返回到标准错误文件(2号fd)
            // 在此过程中会产生很多临时文件-统一存放到temp文件夹中

            pid_t pid = fork();
            if (pid < 0)
            {
                ns_log::LOG(ERROR) << " : fork调用出错" << endl;
                exit(1); // fork错误
            }
            else if (pid == 0) // 子进程
            {
                // 程序替换进行编译程序（需要源文件和目标文件）编译错误时需要重定向

                // file_name->./temp/file_name.cpp
                // file_name->./temp/file_name.ece
                // file_name->./temp/file_name.err

                // 1.重定向
                umask(0);
                int stderr_fd = open(_compile_err.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
                if (stderr_fd < 0)
                {
                    ns_log::LOG(WARNING) << " : open调用出错" << endl;
                    exit(1); // 文件open错误
                }
                dup2(stderr_fd, 2); // 将标准错误重定向到stderr文件描述符中

                // 2.程序替换(执行g++进行编译) 不会影响原文件描述符表
                int ret = execlp("g++", "g++", "-o", _exe.c_str(), _src.c_str(),
                                 "-std=c++14", "-D", "COMPILE_TEST", "-ljsoncpp", nullptr);

                // ***程序替换成功以后，后续代码不会执行（已经被替换掉了）
                if (ret == -1)
                {
                    ns_log::LOG(ERROR) << " : execlp调用出错" << endl;
                    exit(2); // 如果程序替换出错
                }
            }
            else // 父进程
            {
                // 1等待子进程退出
                waitpid(pid, nullptr, 0); // 阻塞等待

                // 2.判断子进程的编译情况(通过判断可执行程序是否存在)
                // 有时候编译成功照样会导致生成.exe文件
                if (file_util::FileExit(_exe) && !file_util::FileIsData(_compile_err))
                {
                    ns_log::LOG(INFO) << " : 编译成功" << endl;
                    return true;
                }
            }

            ns_log::LOG(ERROR) << " : 编译失败报错" << endl;
            return false;
        }
    };

};